<?php
/**
 * @Author: feidao@ibantang.com, 2019, all rights reserved
 * @Date: 2019-06-01
 * @Time: 16:22
 * @Brief:
 */

namespace Core\Common\Base\Db;

use Core\Common\Route\Request;

/**
 * 获取一个数据库连接
 * Class Connection
 * @package Core\Common\Base\Db
 */
class Connection
{
    protected static $pool = [];

    /**
     * 每个连接在池里的标识
     * @param $host
     * @param $port
     * @param $dbName
     * @param $username
     * @return string
     */
    private static function getConnectionId($host, $port, $dbName, $username)
    {
        return $host . ":" . $port . ":" . $dbName . ":" . $username;
    }

    /**
     * 标记重连失败
     * @param $host
     * @param $port
     * @param $dbName
     * @param $username
     */
    public static function flagReconnectError($host, $port, $dbName, $username)
    {
        self::cleanConnection($host, $port, $dbName, $username);
    }

    private static function getHostsFromConfig($conf, $key)
    {
        if (empty($conf)) {
            return [];
        }

        if (empty($conf[$key])) {
            return [];
        }

        $hosts       = [];
        $arrPoolHost = explode(',', $conf[$key]);
        foreach ($arrPoolHost as $tmpHost) {
            if (empty(trim($tmpHost))) {
                continue;
            }
            $hosts[] = trim($tmpHost);
        }
        return $hosts;
    }

    /**
     * 获取数据库连接池hosts
     * @param $conf
     * @return array
     */
    private static function getPoolHosts($conf)
    {
        return self::getHostsFromConfig($conf, 'pool_host');
    }

    /**
     * 从数据库池里获取一个连接或新建一个连接
     * @param $conf
     * @param string $dbName 数据库名
     * @param null $driver
     * @return mixed|null
     */
    public static function getDBConnection($conf, $dbName = '', $driver = null)
    {
        // 添加连接池到 host 列表
        $tmpPort  = $conf['port'];
        $username = $conf['username'];

        if (defined("DISABLE_DB_CONNECTION_POOL") && "1" == DISABLE_DB_CONNECTION_POOL) {
            $hosts = [];
        } else {
            $hosts = self::getPoolHosts($conf);
        }

        // 优先使用连接池
        foreach ($hosts as $tmpHost) {
            $connID = self::getConnectionId($tmpHost, $tmpPort, $dbName, $username);
            if (isset(self::$pool[$connID])) {
                return self::$pool[$connID];
            }
        }

        // 其次使用原有的 host 配置
        $tmpShuffle = $conf['host'] ?? [];
        if (empty($tmpShuffle)) {
            return null;
        }

        // 随机选择一个 host
        $selected = array_rand($tmpShuffle);
        $connID   = self::getConnectionId($tmpShuffle[$selected], $tmpPort, $dbName, $username);
        if (isset(self::$pool[$connID])) {
            return self::$pool[$connID];
        }

        $hosts = array_merge($hosts, $tmpShuffle);
        $type  = 'pdo';
        if ($driver) {
            $type = $driver;
        } elseif (defined('CORE_MYSQL_DRIVER')) {
            $type = CORE_MYSQL_DRIVER;
        }

        $db = null;
        foreach ($hosts as $tmpHost) {
            $connID = self::getConnectionId($tmpHost, $conf['port'], $conf['database'], $username);
            if (isset(self::$pool[$connID])) {
                return self::$pool[$connID];
            }
            list($connectRet, $db) = self::createNewConnection($type, $tmpHost, $conf, 1, $hosts);
            if (0 === $connectRet) {
                break;
            }
        }
        return $db;
    }

    private static function createNewConnection($type, $host, $conf, $maxTryTimes = 1, $bakHosts = [])
    {
        $dbName = $conf['database'];
        $db     = null;
        if ($type == 'pdo') {
            $db = new Pdo($host, $conf['port'], $conf['username'], $conf['password'], $dbName, $bakHosts);
        } else {
            $db = new Mysqli($host, $conf['port'], $conf['username'], $conf['password'], $dbName, $bakHosts);
        }

        $ret = $db->connect($maxTryTimes);
        if (0 === $ret) {
            $connID              = self::getConnectionId($host, $conf['port'], $dbName, $conf['username']);
            self::$pool[$connID] = $db;
        }
        return [$ret, $db,];
    }

    public static function reConnectionSuccess($newHost, $newPort, $oldHost, $oldPort, $dbLink, $dbName, $username)
    {
        $newConnId              = self::getConnectionId($newHost, $newPort, $dbName, $username);
        $oldConnId              = self::getConnectionId($oldHost, $oldPort, $dbName, $username);
        self::$pool[$newConnId] = $dbLink;
        if ($newConnId != $oldConnId) {
            unset(self::$pool[$oldConnId]);
        }
    }

    /**
     * 从连接池里清理掉此连接
     * @param $host
     * @param $port
     * @param $dbName
     * @param $username
     */
    private static function cleanConnection($host, $port, $dbName, $username)
    {
        $connId = self::getConnectionId($host, $port, $dbName, $username);
        unset(self::$pool[$connId]);
    }
}
